#include <netdb.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>

const char* get_addr_str(const struct sockaddr_storage* addr, char buf[])
{
    const void *sinx_addr = NULL;
    if (addr->ss_family == AF_INET6) 
    {
        sinx_addr = (const void *)&((struct sockaddr_in6*)addr)->sin6_addr;
    } 
    else if (addr->ss_family == AF_INET) 
    {
        sinx_addr = (const void *)&((struct sockaddr_in*)addr)->sin_addr;
    } 
    else 
    {
        return "illegal address";
    }

    const char *s = inet_ntop(addr->ss_family, sinx_addr, &buf[0], 255);
    uint16_t port = ntohs((addr->ss_family == AF_INET6)? 
                    ((const struct sockaddr_in6 *)addr)->sin6_port : 
                    ((const struct sockaddr_in *)addr)->sin_port);
    {
        char str_port[10] = {0};
        sprintf(str_port, ":%d", port);
        strcat(buf, str_port);
    }

    return s;
}

int main(void)
{
    const char* host = "ip6-localhost";
    //const char* host = "fe80::4ef3:d2c9:ccdd:78b4%enp4s0";
    int port = 11021;
    int res;

    struct sockaddr_storage host_addr;
    {
        struct addrinfo hints;
        struct addrinfo *res = NULL;
        
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = PF_UNSPEC;
        hints.ai_flags = AI_PASSIVE;
        hints.ai_socktype = SOCK_STREAM;
        int sig = getaddrinfo(host, NULL, &hints, &res);

        if (!(0 == sig) && (res != NULL))
        {
            printf("cannot obtain address of %s\n", host);
            exit(-1);
        }

        switch (res->ai_family)
        {
        case AF_INET: 
        {
            struct sockaddr_in *addr4 = (struct sockaddr_in *)(&host_addr);
            memcpy(addr4, res->ai_addr, res->ai_addrlen);
            addr4->sin_port = htons(port);
            addr4->sin_family = AF_INET;
            puts("IPV4....");
        }
        break;
        case AF_INET6: 
        {
            struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(&host_addr);
            memcpy(addr6, res->ai_addr, res->ai_addrlen);
            addr6->sin6_port = htons(port);
            addr6->sin6_family = AF_INET6;
            puts("IPV6....");
        }
        break;
        default:
            perror("cannot decode address");
            exit(-1);
            break;
        }
        freeaddrinfo(res);
    }

    if (0 == fork())
    {
        int server_sockfd = socket(host_addr.ss_family, SOCK_STREAM, 0);
        int client_sockfd;
        struct sockaddr_storage client_addr;
        socklen_t addrlen = sizeof(client_addr);

        if (-1 == server_sockfd)
        {
            perror("create socket");
            exit(-1);
        }

        res = bind(server_sockfd, (struct sockaddr*)&host_addr,
             (host_addr.ss_family == AF_INET6 ? 
             sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))
        );
        if (0 != res)
        {
            perror("bind");
            exit(-1);
        }

        res = listen(server_sockfd, 5);
        if (0 != res)
        {
            perror("listend");
            exit(-1);
        }
        printf("server waiting accept\n");

        client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &addrlen);
        {
            char buf[256] = {0};
            const char* s = get_addr_str(&client_addr, buf);
            printf("client connected, client address: %s\n", s);
        }

        close(client_sockfd);
        close(server_sockfd);
    }
    else
    {
        sleep(5);
        int sock_fd = socket(host_addr.ss_family, SOCK_STREAM, 0);
        res = connect(sock_fd, (struct sockaddr*)(&host_addr),
            (host_addr.ss_family == AF_INET6 ? 
            sizeof(struct sockaddr_in6) : sizeof( struct sockaddr_in))
        );

        if (0 != res)
        {
            perror("connect error");
            exit(-1);
        }

        close(sock_fd);
    }

    return 0;
}